#pragma once

#include "indicator_common.h"

namespace mdtx::online::indicator
{

    // indicators that depends on order of data

    class willr
    {
    private:
        boost::circular_buffer<double> hbuf, lbuf;
        mdtx::online::updater::minmax_updater<double> minmax;
        double insert(double high, double low, double close)
        {
            if (hbuf.full())
            {
                minmax.remove(hbuf.back());
                minmax.remove(lbuf.back());
            }
            minmax.insert(high);
            minmax.insert(low);
            hbuf.push_front(high);
            lbuf.push_front(low);
            double min = minmax.mindeq.front();
            double max = minmax.maxdeq.front();
            return -100.0 * ((min - close) / (max - min));
        }

    public:
        explicit willr(int period)
            : hbuf(period),
              lbuf(period),
              minmax(period)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, InputIt close, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++, *close++);
            }
        }

        void operator()(double high, double low, double close, double &output)
        {
            output = insert(high, low, close);
        }
    };

    class vhf
    {
    private:
        boost::circular_buffer<double> xbuf, dbuf;
        mdtx::online::updater::minmax_updater<double> minmax;
        mdtx::online::updater::sma_updater<MDTX_INDICATOR_KAHAN> md;

        double insert(double x)
        {
            double d = xbuf.empty() ? 0.0 : std::abs(x - xbuf.front());
            if (xbuf.full())
            {
                minmax.remove(xbuf.back());
                md.roll(d, dbuf.back());
            }
            else
            {
                md.insert(d);
            }
            minmax.insert(x);
            xbuf.push_front(x);
            dbuf.push_front(d);
            return (minmax.maxdeq.front() - minmax.mindeq.front()) / md.m;
        }

    public:
        explicit vhf(int period)
            : xbuf(period),
              dbuf(period),
              minmax(period),
              md()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class stosc
    {
    private:
        boost::circular_buffer<double> hbuf, lbuf, kbuf;
        mdtx::online::updater::minmax_updater<double> minmax;
        mdtx::online::updater::sma_updater<MDTX_INDICATOR_KAHAN> ma_fastk;

        double insert(double high, double low, double close)
        {
            if (hbuf.full())
            {
                minmax.remove(hbuf.back());
                minmax.remove(lbuf.back());
            }
            minmax.insert(high);
            minmax.insert(low);
            hbuf.push_front(high);
            lbuf.push_front(low);
            double d = minmax.maxdeq.front() - minmax.mindeq.front();
            double fastk;
            if (d == 0.0)
            {
                fastk = 0.0;
            }
            else
            {
                fastk = 100.0 * (close - minmax.mindeq.front()) / d;
            }
            if (kbuf.full())
            {
                ma_fastk.roll(fastk, kbuf.back());
            }
            else
            {
                ma_fastk.insert(fastk);
            }
            kbuf.push_front(fastk);
            return ma_fastk.m;
        }

    public:
        explicit stosc(int period)
            : hbuf(period),
              lbuf(period),
              kbuf(period),
              minmax(period),
              ma_fastk()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, InputIt close, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++, *close++);
            }
        }

        void operator()(double high, double low, double close, double &output)
        {
            output = insert(high, low, close);
        }
    };

    class minmax
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::minmax_updater<double> mm;

        void insert(double x)
        {
            if (xbuf.full())
            {
                mm.remove(xbuf.back());
            }
            mm.insert(x);
            xbuf.push_front(x);
        }

    public:
        explicit minmax(int period)
            : xbuf(period),
              mm(period)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt min, OutputIt max)
        {
            while (first != last)
            {
                insert(*first++);
                *min++ = mm.mindeq.front();
                *max++ = mm.maxdeq.front();
            }
        }

        void operator()(double input, double &min, double &max)
        {
            insert(input);
            min = mm.mindeq.front();
            max = mm.maxdeq.front();
        }
    };

    class aroonosc
    {
    private:
        boost::circular_buffer<double> hbuf, lbuf;
        mdtx::online::updater::argminmax_updater<double> minmax;

        double insert(double high, double low)
        {
            if (hbuf.full())
            {
                minmax.remove(hbuf.back());
                minmax.remove(lbuf.back());
            }
            minmax.insert(high);
            minmax.insert(low);
            hbuf.push_front(high);
            lbuf.push_front(low);
            double up = 1.0 - static_cast<double>(minmax.maxidx.front()) / hbuf.size();
            double down = 1.0 - static_cast<double>(minmax.minidx.front()) / hbuf.size();
            return 100.0 * (up - down);
        }

    public:
        explicit aroonosc(int period)
            : hbuf(period),
              lbuf(period),
              minmax(period)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++);
            }
        }

        void operator()(double high, double low, double &output)
        {
            output = insert(high, low);
        }
    };

}
